#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/err.h>
#include <linux/compiler.h>
#include <asm/io.h>
#include <asm/uaccess.h>


#define CTRL_LED_CMD    (0x01)

static int dev_open(struct inode *, struct file *);
static ssize_t dev_read(struct file *, char __user *, size_t , loff_t *);
static ssize_t dev_write(struct file *, const char __user *, size_t , loff_t *);
static int dev_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
static int dev_release(struct inode *, struct file *);



struct mydev
{
	unsigned int 			major;
	char 					*dev_name;
	char 					*cls_name;
	char 					*cls_dev_name;
	struct class 			*cls;
	struct class_device 	*cls_dev;
	int 					flags;
	struct file_operations  dev_oper;

    volatile unsigned long  phy_addr;
    volatile unsigned long  *virt_addr;
    unsigned int            addr_len;
    volatile unsigned long  *gpfcon;
    volatile unsigned long  *gpfdat;
};

static struct mydev dev = {
    .major    = 0,
	.dev_name = "leds_dev",         //cat /proc/devices 可以通过此名找打分配的主设备号
	.cls_name = "leds",             //创建目录：/sys/class/firstcls
	.cls_dev_name = "led",          //创建目录：/sys/class/firstcls/firstdev , mdev会用这个名字创建设备节点名/dev/firstdev
	.cls = NULL,
	.cls_dev = NULL,
	.flags = 0,
	.dev_oper = {
		.owner		= THIS_MODULE,
		.open 		= dev_open,
		.read 		= dev_read,
		.write 		= dev_write,
		.ioctl      = dev_ioctl,
		.release	= dev_release, 
	},

    .phy_addr       = 0x56000050,
    .virt_addr      = NULL,
    .addr_len        = 16,
};

typedef struct devinfo
{
    unsigned int which;
    unsigned int onoff;
}devinfo;



static void leds_onoff(devinfo dinfo)
{
    printk(KERN_INFO "LED[%d], ONOFF[%d]\n", dinfo.which, dinfo.onoff);
    if(dinfo.which == 1)
        (dinfo.onoff == 0)? (*dev.gpfdat &= ~(1 << 4)) : (*dev.gpfdat |= (1 << 4));
    else if(dinfo.which == 2)
        (dinfo.onoff == 0)? (*dev.gpfdat &= ~(1 << 5)) : (*dev.gpfdat |= (1 << 5));
    else if(dinfo.which == 4)
        (dinfo.onoff == 0)? (*dev.gpfdat &= ~(1 << 6)) : (*dev.gpfdat |= (1 << 6));
    else if(dinfo.which == 3)
        (dinfo.onoff == 0)? (*dev.gpfdat &= ~((1 << 4) | (1 << 5) | (1 << 6))) : (*dev.gpfdat |= ((1 << 4) | (1 << 5) | (1 << 
        6)));
}

/*
 * @func: device open
 */
static int dev_open(struct inode *node, struct file *filep)
{
	printk(KERN_INFO "open device\n");
    //[0]. 将LEDS的物理地址映射到虚拟地址
    dev.virt_addr =  (volatile unsigned long *)ioremap(dev.phy_addr, dev.addr_len);
    if(!dev.virt_addr)
    {
        printk(KERN_ERR "Map the leds's physical address to virtual address failed.");
        goto ERR;
    }
    //[1]. 初始化LED引脚
    dev.gpfcon = dev.virt_addr;
    dev.gpfdat = dev.virt_addr + 1;

    *dev.gpfcon &= ~((3 << 8) | (3 << 10) | (3 << 12));  //将GPF4、GPF5、GPF6清零
    *dev.gpfcon |= ((1 << 8) | (1 << 10) | (1 << 12));  //将GPF4、GPF5、GPF6配置为输出
    *dev.gpfdat |= ((1 << 4) | (1 << 5) | (1 << 6));     //将GPF4、GPF5、GPF6拉高，使LED默认为熄灭状态 
    
	return 0;
ERR:
    return -1;
}

/*
 * @func: device read
 */
static ssize_t dev_read(struct file *filep, char __user *buffer, size_t size, loff_t *offset)
{
	printk(KERN_INFO "read device\n");
    //[0]. 将LEDS的物理地址映射到虚拟地址
	return 0;
}

/*
 * @func: device write
 */
static ssize_t dev_write(struct file *filep, const char __user *buffer, size_t size, loff_t *offset)
{
	printk(KERN_INFO "write device\n");
    devinfo dinfo;
    copy_from_user((void *)&dinfo, (const void __user *)buffer, size);
    leds_onoff(dinfo);
	return 0;
}

/*
 * device ioctl
 */
static int dev_ioctl(struct inode *node, struct file *filep, unsigned int cmd, unsigned long arg)
{
    printk(KERN_INFO "ioctl device...");
    switch(cmd)
    {
        case CTRL_LED_CMD:
        {
            printk(KERN_INFO "ioctl led cmd");
            devinfo *dinfo =(devinfo *)arg;
            leds_onoff(*dinfo);
            break;
        }
        default:
            break;
    }
    return 0;
}


/*
 * @func: device close
 */

static int dev_release(struct inode *node, struct file *filep)
{

	printk(KERN_INFO "release device\n");
	return 0;
}



static int __init leddrv_init(void)
{
    printk(KERN_INFO "The driver 'led_drv' is inserted!\n");
	//[0]. 注册一个字符设备
 	dev.major = register_chrdev(0, dev.dev_name, &dev.dev_oper);
    if(dev.major < 0)
	{
		printk(KERN_ERR "Register char device[%s] failed", dev.dev_name);
		goto ERR;
	}
	//[1]. 将字符设备信息注册到sys文件系统中
	dev.cls = class_create(THIS_MODULE, dev.cls_name);
	if(IS_ERR(dev.cls))
	{
		printk(KERN_ERR "Create class [%s] failed", dev.cls_name);
		unregister_chrdev(dev.major, dev.dev_name);
		goto ERR;
	}
	dev.cls_dev = class_device_create(dev.cls, NULL, MKDEV(dev.major, 0), NULL, "%s", dev.cls_dev_name);
	if(unlikely(IS_ERR(dev.cls_dev)))
	{
        printk(KERN_ERR "Create class device [%s] failed", dev.cls_dev_name);
        unregister_chrdev(dev.major, dev.dev_name);
	    class_destroy(dev.cls);
        goto ERR;
    }
    return 0;
ERR:
    printk(KERN_ERR "The driver 'led_drv' register failed\n");

	dev.flags = -1;
	return -1;
}
static void __exit leddrv_exit(void)
{
    printk(KERN_INFO "The driver 'led_drv' is removed!\n");
	if(!dev.flags)
	{
		unregister_chrdev(dev.major, dev.dev_name);
        class_device_unregister(dev.cls_dev);
		class_destroy(dev.cls);
	}
    if(dev.virt_addr)
    {
        iounmap(dev.virt_addr);
        dev.virt_addr = NULL;
    }
}



module_init(leddrv_init);
module_exit(leddrv_exit);


MODULE_LICENSE("GPL");

